package com.example.kotlincoroutine

import kotlinx.coroutines.*
import org.junit.Test
import java.time.LocalDateTime
import kotlin.random.Random
import kotlin.system.measureTimeMillis

class ExampleUnitTest {
    @Test
    fun testCoroutineBuilder() = runBlocking {
        val job: Job = launch {
            delay(2000)
            println("launch: Hello")
        }
        val job2: Deferred<String> = async {
            delay(1900)
            println("async: Hello")
            "async return"
        }
        println(job2.await())
    }

    @Test
    fun testCoroutineJoin() = runBlocking {
        val job: Job = launch {
            delay(2000)
            println("One " + LocalDateTime.now())
        }
        job.join()
        val job2: Job = launch {
            delay(200)
            println("Two " + LocalDateTime.now())
        }
        val job3: Job = launch {
            delay(200)
            println("Three " + LocalDateTime.now())
        }
    }

    @Test
    fun testAsync() = runBlocking {
        measureTimeMillis {
            val one = async {
                delay(1000)
                25
            }
            val two = async {
                delay(1000)
                14
            }
            println(one.await() + two.await())
        }.let { println("Time taken: $it") } // Time taken: 1036
    }

    @Test
    fun testStartMode() = runBlocking {
//        val coroutineStart = CoroutineStart.DEFAULT // 协程体不是立即执行, 线程不同
        val coroutineStart = CoroutineStart.UNDISPATCHED // 协程体立即执行, 线程相同
        launch(context = Dispatchers.IO, start = coroutineStart) {
            println(Thread.currentThread().name + " ---- " + LocalDateTime.now())
            repeat(1000) {
                println("Hello $it")
            }
            println("finish " + LocalDateTime.now())
        }
        println("cancel ${Thread.currentThread().name} ---- " + LocalDateTime.now())
    }

    @Test
    fun testCoroutineScope() = runBlocking {
        coroutineScope {
            val job = launch {
                delay(400)
                println("job1 finish " + LocalDateTime.now())
            }
            val job2 = async {
                delay(200)
                println("job2 finish " + LocalDateTime.now())
                throw RuntimeException("job2 error")
                "job2 result"
            }
        }
    }

    /**
     * 取消作用域会取消它的子协程
     */
    @Test
    fun testScopeCancel() = runBlocking {
        val scope = CoroutineScope(Dispatchers.Default)
        scope.launch {
            delay(1000)
            println("job1 finish " + LocalDateTime.now())
        }
        scope.launch {
            delay(1000)
            println("job2 finish " + LocalDateTime.now())
        }
        scope.cancel() // job1 job2 不会执行
        delay(2000)
    }

    /**
     * 被取消的子协程不会影响其余兄弟协程
     */
    @Test
    fun testBrotherCancel() = runBlocking {
        val scope = CoroutineScope(Dispatchers.Default)
        val job = scope.launch {
            delay(1000)
            println("job1 finish " + LocalDateTime.now())
        }
        scope.launch {
            delay(1000)
            println("job2 finish " + LocalDateTime.now())
        }
        job.cancel() // job2 依然会执行
        delay(2000)
    }

    /**
     * 协程通过抛出 CancellationException 来处理取消操作
     */
    @Test
    fun testCancellationException() = runBlocking {
        val job1 = GlobalScope.launch {
            try {
                delay(1000)
                println("job1 finish " + LocalDateTime.now())
            } catch (e: CancellationException) {
                e.printStackTrace()
                println("job1 cancel")
            }
        }
//        job1.cancel()
//        job1.join() // 注释掉这句代码不会执行取消操作
        job1.cancelAndJoin() // 取消并等待，等同于上面两句代码
    }

    @Test
    fun testCancelCpuTaskByIsActive() = runBlocking {
        val job = launch(Dispatchers.Default) {
            val startTime = System.currentTimeMillis()
            var nextPrintTime = startTime
            var i = 0
            while (i < 5 && isActive) {
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping ${i++} ...")
                    nextPrintTime += 1000L
                }
            }
        }
        delay(1000)
        println("main: I'm tired of waiting!")
        job.cancelAndJoin()
        println("main: Now I can quit.")
    }

    @Test
    fun testCancelCpuTaskByEnsureActive() = runBlocking {
        val job = launch(Dispatchers.Default) {
            val startTime = System.currentTimeMillis()
            var nextPrintTime = startTime
            var i = 0
            while (i < 5) {
                try {
                    ensureActive()
                } catch (e: CancellationException) {
                    e.printStackTrace()
                    println("Canceled")
                    break
                }
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping ${i++} ...")
                    nextPrintTime += 1000L
                }
            }
        }
        delay(1000)
        println("main: I'm tired of waiting!")
        job.cancelAndJoin()
        println("main: Now I can quit.")
    }

    @Test
    fun testCancelCpuTaskByYield() = runBlocking {
        val job = launch(Dispatchers.Default) {
            val startTime = System.currentTimeMillis()
            var nextPrintTime = startTime
            var i = 0
            while (i < 5) {
                try {
                    yield()
                } catch (e: CancellationException) {
                    e.printStackTrace()
                    println("Canceled")
                    break
                }
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping ${i++} ...")
                    nextPrintTime += 1000L
                }
            }
        }
        delay(1000)
        println("main: I'm tired of waiting!")
        job.cancelAndJoin()
        println("main: Now I can quit.")
    }

    /*
    输出结果：
    job1: I'm sleeping 0 ...
    job1: I'm sleeping 1 ...
    job1: I'm sleeping 2 ...
    job1 cancel
    job1: I'm running finally
    main: Now I can quit.
     */
    @Test
    fun testRelease() = runBlocking {
        val job = launch {
            try {
                repeat(1000) {
                    println("job1: I'm sleeping $it ...")
                    delay(500)
                }
            } catch (e: CancellationException) {
                println("job1 cancel")
            } finally {
                println("job1: I'm running finally")
            }
        }
        delay(1300)
        job.cancelAndJoin()
        println("main: Now I can quit.")
    }

    /*
    输出结果：
    job1: I'm sleeping 0 ...
    job1: I'm sleeping 1 ...
    job1: I'm sleeping 2 ...
    job1 cancel
    job1: I'm running finally
    job 1: Now I can quit.
    main: Now I can quit.
     */
    @Test
    fun testNonCancellable() = runBlocking {
        val job = launch {
            try {
                repeat(1000) {
                    println("job1: I'm sleeping $it ...")
                    delay(500)
                }
            } catch (e: CancellationException) {
                println("job1 cancel")
            } finally {
                withContext(NonCancellable) {
                    println("job1: I'm running finally")
                    delay(1000L)
                    println("job 1: Now I can quit.")
                }
            }
        }
        delay(1300)
        job.cancelAndJoin()
        println("main: Now I can quit.")
    }

    /*
    输出结果：
    是否通过: false
    result: null

    是否通过: true
    result: Done
     */
    @Test
    fun testTimeout() = runBlocking {
    // withTimeout() // 抛出异常 TimeoutCancellationException
        val result = withTimeoutOrNull(1300) {
            val boolean = Random.nextBoolean()
            println("是否通过: $boolean")
            delay(if (boolean) 1000 else 2000)
            "Done"
        }
        println("result: $result")
    }
}